// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <inttypes.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/atomic.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/vfs.h>
#include <fs/vfs.h>
#include <lib/memfs/cpp/vnode.h>
#include <lib/memfs/memfs.h>
#include <lib/sync/completion.h>
#include <zircon/device/vfs.h>

#include "dnode.h"

namespace {

constexpr size_t kPageSize = static_cast<size_t>(PAGE_SIZE);

}

namespace memfs {

zx_status_t Vfs::CreateFromVmo(VnodeDir* parent, fbl::StringPiece name,
                               zx_handle_t vmo, zx_off_t off,
                               zx_off_t len) {
    fbl::AutoLock lock(&vfs_lock_);
    return parent->CreateFromVmo(name, vmo, off, len);
}

void Vfs::MountSubtree(VnodeDir* parent, fbl::RefPtr<VnodeDir> subtree) {
    fbl::AutoLock lock(&vfs_lock_);
    parent->MountSubtree(fbl::move(subtree));
}

zx_status_t Vfs::FillFsId() {
    if (fs_id_) {
        return ZX_OK;
    }
    zx::event event;
    zx_status_t status = zx::event::create(0, &event);
    if (status != ZX_OK) {
        return status;
    }
    zx_info_handle_basic_t info;
    status = event.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
    if (status != ZX_OK) {
        return status;
    }

    fs_id_ = info.koid;
    return ZX_OK;
}

zx_status_t Vfs::GrowVMO(zx::vmo& vmo, size_t current_size,
                         size_t request_size, size_t* actual_size) {
    if (request_size <= current_size) {
        *actual_size = current_size;
        return ZX_OK;
    }
    size_t aligned_len = fbl::round_up(request_size, kPageSize);
    ZX_DEBUG_ASSERT(current_size % kPageSize == 0);
    size_t num_new_pages = (aligned_len - current_size) / kPageSize;
    if (num_new_pages + num_allocated_pages_ > pages_limit_) {
        *actual_size = current_size;
        return ZX_ERR_NO_SPACE;
    }
    zx_status_t status;
    if (!vmo.is_valid()) {
        if ((status = zx::vmo::create(aligned_len, 0, &vmo)) != ZX_OK) {
            return status;
        }
    } else {
        if ((status = vmo.set_size(aligned_len)) != ZX_OK) {
            return status;
        }
    }
    // vmo operation succeeded
    num_allocated_pages_ += num_new_pages;
    *actual_size = aligned_len;
    return ZX_OK;
}

void Vfs::WillFreeVMO(size_t vmo_size) {
    ZX_DEBUG_ASSERT(vmo_size % kPageSize == 0);
    size_t freed_pages = vmo_size / kPageSize;
    ZX_DEBUG_ASSERT(freed_pages <= num_allocated_pages_);
    num_allocated_pages_ -= freed_pages;
}

fbl::atomic<uint64_t> VnodeMemfs::ino_ctr_ = 0;
fbl::atomic<uint64_t> VnodeMemfs::deleted_ino_ctr_ = 0;

VnodeMemfs::VnodeMemfs(Vfs* vfs) : dnode_(nullptr), link_count_(0), vfs_(vfs),
    ino_(ino_ctr_.fetch_add(1, fbl::memory_order_relaxed)) {
    create_time_ = modify_time_ = zx_clock_get(ZX_CLOCK_UTC);
}

VnodeMemfs::~VnodeMemfs() {
    deleted_ino_ctr_.fetch_add(1, fbl::memory_order_relaxed);
}

zx_status_t VnodeMemfs::Setattr(const vnattr_t* attr) {
    if ((attr->valid & ~(ATTR_MTIME)) != 0) {
        // only attr currently supported
        return ZX_ERR_INVALID_ARGS;
    }
    if (attr->valid & ATTR_MTIME) {
        modify_time_ = attr->modify_time;
    }
    return ZX_OK;
}

void VnodeMemfs::Sync(SyncCallback closure) {
    // Since this filesystem is in-memory, all data is already up-to-date in
    // the underlying storage
    closure(ZX_OK);
}

zx_status_t VnodeMemfs::AttachRemote(fs::MountChannel h) {
    if (!IsDirectory()) {
        return ZX_ERR_NOT_DIR;
    } else if (IsRemote()) {
        return ZX_ERR_ALREADY_BOUND;
    }
    SetRemote(fbl::move(h.TakeChannel()));
    return ZX_OK;
}

zx_status_t CreateFilesystem(const char* name, memfs::Vfs* vfs, fbl::RefPtr<VnodeDir>* out) {
    zx_status_t status;
    if ((status = vfs->FillFsId()) != ZX_OK) {
        return status;
    }
    fbl::AllocChecker ac;
    fbl::RefPtr<VnodeDir> fs = fbl::AdoptRef(new (&ac) VnodeDir(vfs));
    if (!ac.check()) {
        return ZX_ERR_NO_MEMORY;
    }

    fbl::RefPtr<Dnode> dn = Dnode::Create(name, fs);
    if (dn == nullptr) {
        return ZX_ERR_NO_MEMORY;
    }

    fs->dnode_ = dn; // FS root is directory
    *out = fs;
    return ZX_OK;
}

} // namespace memfs
